package com.wallace.downloadserver.util

import com.wallace.downloadserver.redis.RedisManager
import com.wallace.downloadserver.redis.getV
import com.wallace.downloadserver.redis.setV
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.core.io.ClassPathResource
import org.springframework.data.redis.core.RedisTemplate
import org.springframework.stereotype.Component
import java.io.File
import java.io.IOException

/**
 * @Description:
 * （大）文件上传工具类。
 * @author Wallace
 * @Date 2022/3/18 22:17
 */
@Component
class FileUploadUtil {

    private val log: Logger = LoggerFactory.getLogger(FileUploadUtil::class.java)

    private var redisFileDB: RedisTemplate<String, Any>? = null

    companion object {
        /**
         * 在redis中，存储文件时Key的命名格式:
         * 用户uid-文件目录-文件名
         * "uid-parentFileName-fileName"
         * eg:"202201120000001-uploadTest-fileName1"
         * 因为一个（大）文件可能分为几个小文件上传和存储，
         * 所以value表示这几个小文件的MD5值的集合
         * eg:"[md5_1 ,md5_2]"
         */
        private const val KeyFormat = "%s-%s-%s"
    }

    @Autowired
    private fun setRedisFileDB(redisManager: RedisManager) {
        redisFileDB = redisManager.getFileDB()
    }

    /**
     * 在文件上传前创建Key
     * @param uid 用户唯一标识
     * @param fileName1 文件存放的目录
     * @param fileName2 文件名
     * @param total 总文件被切割成小文件的数量
     */
    fun create(uid: String, fileName1: String, fileName2: String, total: Int = 1) {
        val key = String.format(KeyFormat, uid, fileName1, fileName2)
        val value = ArrayList<String>(total)
        log.debug("value = ${value.joinToString(separator = ",")}")
        redisFileDB?.setV(key, value)
    }

    /**
     * 创建一个临时文件
     * @param md5 文件的md5值，这里用作文件名
     */
    fun createTempFile(md5: String): File? {
        val classPathResource = ClassPathResource("temp/${md5}")
        val file = classPathResource.file
        if (file.exists() && file.isFile) {
            file.writeText("")
        } else {
            try {
                file.createNewFile()
            } catch (e: IOException) {
                log.debug(e.toString())
                return null
            } catch (e: SecurityException) {
                log.debug(e.toString())
                return null
            }
        }
        return file
    }

    /**
     * 查询某个文件是否已完整的存在
     * @param uid 用户唯一标识
     * @param fileName1 文件存放的目录
     * @param fileName2 文件名
     * @param part 查询大文件的某个小文件
     * @param total 总文件被切割成小文件的数量
     * @param md5 要查询的小文件的MD5
     */
    @Throws(IllegalArgumentException::class)
    fun selectFile(uid: String, fileName1: String, fileName2: String, part: Int? = null, total: Int = 1, md5: String? = null): Boolean {
        // 判断要查询的是大文件还是大文件中的某一部分
        val selectBigFile = isBigFile()
        val key = String.format(KeyFormat, uid, fileName1, fileName2)
        val value = redisFileDB?.getV(key) as? ArrayList<*>
        value?.apply {
            if (selectBigFile) {
                for (i in 0 until total) {
                    if ((value[part!!.minus(-1)] as? String).isNullOrEmpty()) {
                        continue
                    } else {
                        return false
                    }
                }
                return true
            } else {
                return try {
                    value[part!!.minus(-1)] as? String == md5
                } catch (e: IndexOutOfBoundsException) {
                    log.warn(e.toString())
                    return false
                }
            }
        }
        return false
    }

    /**
     * 文件部分上传完毕，把小文件的md5存入redis中
     * @param uid 用户唯一标识
     * @param fileName1 文件存放的目录
     * @param fileName2 文件名
     * @param part 大文件的某个小文件
     * @param total 总文件被切割成小文件的数量
     * @param md5 小文件的MD5
     */
    @Throws(IllegalArgumentException::class)
    fun update(uid: String, fileName1: String, fileName2: String, part: Int = 1, total: Int = 1, md5: String = "") {
        if (part < 1) {
            throw IllegalArgumentException("part = $part, but part must be greater than 1")
        }
        if (md5.isEmpty()) {
            throw IllegalArgumentException("md5 = $md5, but md5 must not be empty")
        }
        if (part > total) {
            throw IllegalArgumentException("part = $part and total = $total, but part must not be greater than total")
        }
        val key = String.format(KeyFormat, uid, fileName1, fileName2)
        val value = redisFileDB?.getV(key) as? ArrayList<String>
        if (total != value?.size) {
            throw IllegalArgumentException("total = $part, but the given total is not equal to the actual size")
        }
        value[part.minus(1)] = md5
        redisFileDB?.setV(key, value)
    }

    /**
     * 查询某个文件是否已完整的存在
     * @param uid 用户唯一标识
     * @param fileName1 文件存放的目录
     * @param fileName2 文件名
     * @param part 查询大文件的某个小文件
     * @param total 总文件被切割成小文件的数量
     * @param md5 要查询的小文件的MD5
     */
    @Throws(IllegalArgumentException::class)
    fun deleteFile(uid: String, fileName1: String, fileName2: String, part: Int? = null, total: Int = 1, md5: String? = null) {
        // 判断要查询的是大文件还是大文件中的某一部分
        val deleteBigFile = isBigFile()
        val key = String.format(KeyFormat, uid, fileName1, fileName2)
        var value = redisFileDB?.getV(key) as? ArrayList<String>
        if (value != null) {
            if (deleteBigFile) {
                value = ArrayList(value.size)
                redisFileDB?.setV(key, value)
            } else {
                value[part!!.minus(1)] = ""
                redisFileDB?.setV(key, value)
            }
        }
    }

    @Throws(IllegalArgumentException::class)
    private fun isBigFile(part: Int? = null, md5: String? = null): Boolean {
        return if (part == null && md5 == null) {
            true
        } else if (part != null && md5 != null) {
            false
        } else if (part != null && md5 == null) {
            throw IllegalArgumentException("part = $part, but md5 = null")
        } else {
            throw IllegalArgumentException("part = null, but md5 = $md5")
        }
    }
}